package com.qiguliuxing.dts.service.merchant;

import com.github.pagehelper.PageHelper;
import com.qiguliuxing.dts.common.type.MerchantTraceTypeEnum;
import com.qiguliuxing.dts.common.util.CharUtils;
import com.qiguliuxing.dts.common.util.DateTimeUtils;
import com.qiguliuxing.dts.db.dao.DtsMerchantAccountMapper;
import com.qiguliuxing.dts.db.dao.DtsMerchantAccountTraceMapper;
import com.qiguliuxing.dts.db.dao.DtsMerchantDailyStatisMapper;
import com.qiguliuxing.dts.db.dao.ex.StatMapper;
import com.qiguliuxing.dts.db.domain.*;
import com.qiguliuxing.dts.service.system.SystemConfig;
import org.slf4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * 商户账单统计服务
 * @author qiguliuxing
 * @since 1.0.0
 */
@Service
public class BillStatisService {
    private static final Logger logger = getLogger(BillStatisService.class);

    @Resource
    private DtsMerchantAccountMapper dtsMerchantAccountMapper;
    @Resource
    private DtsMerchantDailyStatisMapper dtsMerchantDailyStatisMapper;
    @Resource
    private DtsMerchantAccountTraceMapper dtsMerchantAccountTraceMapper;
    @Resource
    private StatMapper statMapper;

    /**
     * 统计指定商铺上一个交易日运营情况
     * @param brandId
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })
    public void mechantDailyStatis(Integer brandId) {
        LocalDateTime yesterdayDateTime = LocalDateTime.now().plusDays(-1);
        String yesterday = DateTimeUtils.getDateDisplayString(yesterdayDateTime);
        DtsMerchantDailyStatis dtsMerchantDailyStatis = merchantDailyStatis(brandId, yesterday);
        if (dtsMerchantDailyStatis != null) {
            // 1. 检查店铺账号是否存在，不存在需新增
            DtsMerchantAccount mechantAccount = checkAndAddBrandAccount(brandId);

            /**
             * 如果是重复统计，即对应日期已经统计过，本次未重复统计 则需要以本次最新的为准：
             * a. 判断统计结果与数据库中的是否一致（如果一致则后续操作结束），不一致则继续 b,c,d 操作
             * b. 则更新原有的统计数据，
             * c. 账号金额需要根据统计的差异进行处理
             * d. 账号跟踪记录无需更新（本次依然是新增）,操作资金为差异值
             */
            DtsMerchantDailyStatis dbDailyStatis = getBrandDailyStatis(brandId, yesterday);
            if (dbDailyStatis != null) {
                if (checkSameDailyStatis(dtsMerchantDailyStatis, dbDailyStatis)) {
                    logger.warn("店铺{}，日期：{} ，重复统计 ，本次忽略！", brandId, yesterday);
                    return ;
                } else {
                    // 不一致，则以数据更新为主
                    DtsMerchantDailyStatisExample example = new DtsMerchantDailyStatisExample();
                    example.or().andBrandIdEqualTo(brandId).andStatisDayEqualTo(LocalDate.parse(yesterday));
                    dtsMerchantDailyStatisMapper.updateByExampleSelective(dtsMerchantDailyStatis, example);

                    // 获取两次的实际入账差异值
                    BigDecimal diffActualAmount = dtsMerchantDailyStatis.getActualAmount().subtract(dbDailyStatis.getActualAmount());

                    // 生成一条系统结算的账号跟踪记录
                    DtsMerchantAccountTrace dtsMerchantAccountTrace = generateMerchantAccountTrace(brandId, mechantAccount.getRemainAmount(), diffActualAmount, MerchantTraceTypeEnum.SYS_SETTLEMENT.type);
                    dtsMerchantAccountTraceMapper.insert(dtsMerchantAccountTrace);

                    mechantAccount.setTotalAmount(mechantAccount.getTotalAmount().add(diffActualAmount));
                    mechantAccount.setRemainAmount(mechantAccount.getRemainAmount().add(diffActualAmount));
                    mechantAccount.setUpdateTime(LocalDateTime.now());
                    dtsMerchantAccountMapper.updateByPrimaryKey(mechantAccount);

                    logger.warn("店铺{}，日期：{} ，重复统计 ，本次统计数据变动更新！", brandId, yesterday);
                    return ;
                }
            }

            // 2. 存储统计数据
            dtsMerchantDailyStatis.setAddTime(LocalDateTime.now());
            dtsMerchantDailyStatis.setBrandId(brandId);
            dtsMerchantDailyStatis.setStatisDay(LocalDate.now().plusDays(-1));
            dtsMerchantDailyStatisMapper.insert(dtsMerchantDailyStatis);

            // 3. 生成一条系统结算的账号跟踪记录
            DtsMerchantAccountTrace dtsMerchantAccountTrace = generateMerchantAccountTrace(brandId, mechantAccount.getRemainAmount(), dtsMerchantDailyStatis.getActualAmount(), MerchantTraceTypeEnum.SYS_SETTLEMENT.type);
            dtsMerchantAccountTraceMapper.insert(dtsMerchantAccountTrace);

            // 4. 更新店铺账号数据
            mechantAccount.setTotalAmount(mechantAccount.getTotalAmount().add(dtsMerchantDailyStatis.getActualAmount()));
            mechantAccount.setRemainAmount(mechantAccount.getRemainAmount().add(dtsMerchantDailyStatis.getActualAmount()));
            mechantAccount.setUpdateTime(LocalDateTime.now());
            dtsMerchantAccountMapper.updateByPrimaryKey(mechantAccount);

        } else {
            logger.warn("警告：统计商铺日运营情况，未获取到店铺：{} 交易日：{} 的订单数据！", brandId, yesterday);
        }
    }

    /**
     * 检查两个统计数据是否一致
     *
     * @param dtsMerchantDailyStatis
     * @param dbDailyStatis
     * @return
     */
    private boolean checkSameDailyStatis(DtsMerchantDailyStatis dtsMerchantDailyStatis, DtsMerchantDailyStatis dbDailyStatis) {
        // 日期不同则不一致
        if (dtsMerchantDailyStatis.getStatisDay().isEqual(dbDailyStatis.getStatisDay())) {
            return false;
        }
        // 实际金额不同，则不一致
        if (dtsMerchantDailyStatis.getActualAmount().compareTo(dbDailyStatis.getActualAmount()) != 0) {
            return false;
        }
        // 订单笔数不同，则不一致
        if (dtsMerchantDailyStatis.getOrderCnt().intValue() != dbDailyStatis.getOrderCnt().intValue()) {
            return false;
        }
        // 退款笔数不同，则不一致
        if (dtsMerchantDailyStatis.getRefundCnt().intValue() != dbDailyStatis.getRefundCnt().intValue()) {
            return false;
        }
        // 总订单金额（以计算的商品金额与优惠金额）不同，则不一致
        if (dtsMerchantDailyStatis.getOrderAmount().compareTo(dbDailyStatis.getOrderAmount()) != 0) {
            return false;
        }
        // 总退款金额不同，则不一致
        if (dtsMerchantDailyStatis.getRefundAmount().compareTo(dbDailyStatis.getRefundAmount()) != 0) {
            return false;
        }

        return true;
    }

    private DtsMerchantDailyStatis getBrandDailyStatis(Integer brandId, String day) {
        DtsMerchantDailyStatisExample example = new DtsMerchantDailyStatisExample();
        example.or().andBrandIdEqualTo(brandId).andStatisDayEqualTo(LocalDate.parse(day));
        return dtsMerchantDailyStatisMapper.selectOneByExample(example);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })
    DtsMerchantAccount checkAndAddBrandAccount(Integer brandId) {
        DtsMerchantAccountExample example = new DtsMerchantAccountExample();
        example.or().andBrandIdEqualTo(brandId);
        DtsMerchantAccount mechantAccount = dtsMerchantAccountMapper.selectOneByExample(example);

        // 未创建商铺资金账号，则需要新建
        if (mechantAccount == null) {
            mechantAccount = new DtsMerchantAccount();
            mechantAccount.setBrandId(brandId);
            // 默认正常有效状态
            mechantAccount.setStatus((byte) 1);
            mechantAccount.setCreateTime(LocalDateTime.now());
            mechantAccount.setRemainAmount(new BigDecimal("0"));
            mechantAccount.setTotalAmount(new BigDecimal("0"));
            dtsMerchantAccountMapper.insert(mechantAccount);
        }

        return mechantAccount;
    }

    /**
     * 统计某个商品对应交易日运营情况
     *  <p>
     *    <li>统计订单笔数</li>
     *    <li>统计退款笔数</li>
     *    <li>统计核销券数量</li>
     *    <li>总商品金额</li>
     *    <li>总优惠金额=优惠券减免+用户积分减免+团购减免+秒杀减免</li>
     *    <li>总物流费</li>
     *    <li>总订单金额=总商品金额+总物流费-总优惠券金额</li>
     *    <li>总退款金额</li>
     *    <li>退款冲正金额:即非下单日退款订单的代理结算佣金</li>
     *    <li>代理结算佣金费(去除退款订单)</li>
     *    <li>运营费=交易手续,其他物流短信运营费等,基于订单总金额乘以一个手续费系数</li>
     *    <li>实际收入=总订单金额+退款冲正金额-总退款金额-代理结算佣金费-运营费</li>
     *  </p>
     * @param brandId 商户id
     * @param day 统计日期：格式 yyyy-mm-dd
     */
    public DtsMerchantDailyStatis merchantDailyStatis(Integer brandId, String day) {
        String startTime = day + " 00:00:00";
        String endTime = day + " 23:59:59";
        // 支付订单部分的统计
        DtsMerchantDailyStatis orderDailyStatis = statMapper.merchantOrderDailyStatis(brandId, startTime, endTime);
        if (orderDailyStatis != null && orderDailyStatis.getOrderCnt() == 0) {
            orderDailyStatis = null;
        }
        // 如果有订单统计数据，且减免金额大于0 则需要查询优惠券核销数量
        if (orderDailyStatis != null && orderDailyStatis.getDiscountAmount().compareTo(new BigDecimal(0)) > 0) {
            Long couponCnt = statMapper.merchantDailyCouponStatis(brandId, startTime, endTime);
            orderDailyStatis.setCouponCnt(couponCnt.intValue());
        }

        // 退款订单部分的统计
        DtsMerchantDailyStatis refundDailyStatis = statMapper.merchantRefundDailyStatis(brandId, startTime, endTime);
        if (refundDailyStatis != null && refundDailyStatis.getRefundCnt() == 0) {
            refundDailyStatis = null;
        }
        return mergeDailyStatisData(brandId, orderDailyStatis, refundDailyStatis);
    }

    /**
     * 合并商户的日统计数据
     * @param brandId
     * @param orderDailyStatis
     * @param refundDailyStatis
     * @return
     */
    private DtsMerchantDailyStatis mergeDailyStatisData(Integer brandId, DtsMerchantDailyStatis orderDailyStatis, DtsMerchantDailyStatis refundDailyStatis) {
        DtsMerchantDailyStatis dtsMerchantDailyStatis = null;
        if (orderDailyStatis != null || refundDailyStatis != null) {
            dtsMerchantDailyStatis = new DtsMerchantDailyStatis();
            dtsMerchantDailyStatis.setBrandId(brandId);

            // 统计日期
            LocalDate statisDay = orderDailyStatis == null ? refundDailyStatis.getStatisDay() : orderDailyStatis.getStatisDay();
            dtsMerchantDailyStatis.setStatisDay(statisDay);

            // 订单笔数
            int orderCnt = orderDailyStatis == null || orderDailyStatis.getOrderCnt() == null? 0 : orderDailyStatis.getOrderCnt();
            dtsMerchantDailyStatis.setOrderCnt(orderCnt);

            // 退款笔数
            int refundCnt = refundDailyStatis == null || orderDailyStatis.getRefundCnt() == null ? 0 : refundDailyStatis.getRefundCnt();
            dtsMerchantDailyStatis.setRefundCnt(refundCnt);

            // 核销优惠券数量
            int couponCnt = orderDailyStatis == null || orderDailyStatis.getCouponCnt() == null ? 0 : orderDailyStatis.getCouponCnt();
            dtsMerchantDailyStatis.setCouponCnt(couponCnt);

            // 销售商品的总额
            BigDecimal goodsAmount = orderDailyStatis == null ? new BigDecimal(0) : orderDailyStatis.getGoodsAmount();
            dtsMerchantDailyStatis.setGoodsAmount(goodsAmount);

            // 总优惠金额
            BigDecimal discountAmount = orderDailyStatis == null ? new BigDecimal(0) : orderDailyStatis.getDiscountAmount();
            dtsMerchantDailyStatis.setDiscountAmount(discountAmount);

            // 总物流费用
            BigDecimal freightAmount = orderDailyStatis == null ? new BigDecimal(0) : orderDailyStatis.getFreightAmount();
            dtsMerchantDailyStatis.setFreightAmount(freightAmount);

            // 总订单金额
            BigDecimal orderAmount = orderDailyStatis == null ? new BigDecimal(0) : orderDailyStatis.getOrderAmount();
            dtsMerchantDailyStatis.setOrderAmount(orderAmount);

            // 总退款金额
            BigDecimal refundAmount = refundDailyStatis == null ? new BigDecimal(0) : refundDailyStatis.getRefundAmount();
            dtsMerchantDailyStatis.setRefundAmount(refundAmount);

            // 退款冲正金额
            BigDecimal refundReversalAmount = refundDailyStatis == null ? new BigDecimal(0) : refundDailyStatis.getRefundReversalAmount();
            dtsMerchantDailyStatis.setRefundReversalAmount(refundReversalAmount);

            // 代理结算佣金费
            BigDecimal settlementAmount = orderDailyStatis == null ? new BigDecimal(0) : orderDailyStatis.getSettlementAmount();
            dtsMerchantDailyStatis.setSettlementAmount(settlementAmount);

            // 运营费
            BigDecimal busiAmount = new BigDecimal(0);
            if (orderAmount.compareTo(new BigDecimal(0)) > 0) {
                BigDecimal tradeRatio = SystemConfig.tradeRatio();
                busiAmount = orderAmount.multiply(tradeRatio).setScale(2, BigDecimal.ROUND_UP);
            }
            dtsMerchantDailyStatis.setBusiAmount(busiAmount);

            // 实际收入 = 总订单金额+退款冲正金额-总退款金额-代理结算佣金费-运营费
            BigDecimal actualAmount = orderAmount.add(refundReversalAmount).subtract(refundAmount).subtract(settlementAmount).subtract(busiAmount);
            dtsMerchantDailyStatis.setActualAmount(actualAmount);
        }
        return dtsMerchantDailyStatis;
    }

    /**
     * 生成资金操作记录
     * @param brandId
     * @param remainAmount
     * @param amount
     * @param type
     * @return
     */
    private DtsMerchantAccountTrace generateMerchantAccountTrace(Integer brandId, BigDecimal remainAmount, BigDecimal amount, int type) {
        DtsMerchantAccountTrace dtsMerchantAccountTrace = new DtsMerchantAccountTrace();
        // 操作流水生成
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMMdd");
        String now = df.format(LocalDate.now());
        String traceSn = now + CharUtils.getRandomNum(6);
        dtsMerchantAccountTrace.setTraceSn(traceSn);

        dtsMerchantAccountTrace.setAddTime(LocalDateTime.now());
        dtsMerchantAccountTrace.setBrandId(brandId);
        dtsMerchantAccountTrace.setAmount(amount);
        dtsMerchantAccountTrace.setType(type);

        // 如果是系统结算，则标记操作记录状态完成，并设置完成时间
        if (MerchantTraceTypeEnum.SYS_SETTLEMENT.type == type) {
            dtsMerchantAccountTrace.setStatus((byte)1);
            dtsMerchantAccountTrace.setFinishTime(LocalDateTime.now());
            dtsMerchantAccountTrace.setBeforeAmount(remainAmount);
            BigDecimal afterAmount = remainAmount.add(amount);
            dtsMerchantAccountTrace.setAfterAmount(afterAmount);
        }
        return dtsMerchantAccountTrace;
    }

    /**
     * 分页查询企业日账单
     * @param brandId
     * @param page
     * @param size
     * @param sort
     * @param order
     * @return
     */
    public List<DtsMerchantDailyStatis> querySelective(Integer brandId, Integer page, Integer size, String sort, String order, List<String> dateRange) {
        DtsMerchantDailyStatisExample example = new DtsMerchantDailyStatisExample();
        DtsMerchantDailyStatisExample.Criteria criteria = example.createCriteria();

        if (brandId != null) {
            criteria.andBrandIdEqualTo(brandId);
        }
        if (!StringUtils.isEmpty(sort) && !StringUtils.isEmpty(order)) {
            example.setOrderByClause(sort + " " + order);
        }
        // 如果传入完整的时间范围参数，则设置查询时间范围
        if (dateRange != null && dateRange.size() == 2) {
            criteria.andStatisDayGreaterThanOrEqualTo(LocalDate.parse(dateRange.get(0)));
            criteria.andStatisDayLessThanOrEqualTo(LocalDate.parse(dateRange.get(1)));
        }
        PageHelper.startPage(page, size);
        return dtsMerchantDailyStatisMapper.selectByExample(example);
    }

    /**
     * 根据商户号查询商户账号
     * @param brandId
     * @return
     */
    public DtsMerchantAccount getAccountByBrandId(Integer brandId) {
        DtsMerchantAccountExample example = new DtsMerchantAccountExample();
        example.or().andBrandIdEqualTo(brandId);
        return dtsMerchantAccountMapper.selectOneByExample(example);
    }

    /**
     * 获取商户提现中的金额
     * @param brandId
     * @return
     */
    public BigDecimal getApplyWithdrawAmount(Integer brandId) {
        DtsMerchantAccountTraceExample example = new DtsMerchantAccountTraceExample();
        example.or().andBrandIdEqualTo(brandId).andTypeEqualTo(MerchantTraceTypeEnum.MERCHANT_APPLY.type).andStatusIsNull();
        List<DtsMerchantAccountTrace> accountTraces = dtsMerchantAccountTraceMapper.selectByExample(example);

        BigDecimal applyWithdrawAmount = new BigDecimal(0);
        if (accountTraces != null && accountTraces.size() > 0) {
            for (DtsMerchantAccountTrace accountTrace : accountTraces) {
                applyWithdrawAmount.add(accountTrace.getAmount());
            }
        }
        return applyWithdrawAmount;
    }

    /**
     * 检查 提现金额是否满足
     *  条件：提现金额必须小于 可提现金额
     * @param brandId
     * @param withdrawAmount
     * @return
     */
    public boolean withdrawAmountBeyondCheck(Integer brandId, BigDecimal withdrawAmount) {
        DtsMerchantAccount merchantAccount = getAccountByBrandId(brandId);
        BigDecimal applyWithdrawAmount = getApplyWithdrawAmount(brandId);

        // 可提现金额 = 账号剩余金额 - 已申请提现中金额
        BigDecimal withdrawableAmount = merchantAccount.getRemainAmount().subtract(applyWithdrawAmount);
        return withdrawableAmount.compareTo(withdrawAmount) >= 0 ;
    }

    /**
     * 生成提现申请记录
     *  申请需等待审批，未审批之前无需操作账户金额，
     *  申请时 不用存储跟踪记录的状态， 操作前账号资金与操作后用户资金，这些值需审批操作进行补充
     * @param brandId
     * @param withdrawAccount
     * @param withdrawAmount
     */
    public void apply(Integer brandId, DtsMerchantWithdrawalAccount withdrawAccount, BigDecimal withdrawAmount) {
        DtsMerchantAccountTrace dtsMerchantAccountTrace = generateMerchantAccountTrace(brandId, null, withdrawAmount,  MerchantTraceTypeEnum.MERCHANT_APPLY.type);
        dtsMerchantAccountTrace.setAccountNo(withdrawAccount.getAccount());
        dtsMerchantAccountTrace.setStatus((byte)2);
        dtsMerchantAccountTraceMapper.insert(dtsMerchantAccountTrace);
    }
	
	/**
     * 商户提现通过核减账户总额
     * @param dtsMerchantAccount 账务信息
     * @return            状态
     */
    public int subtractRemainAmount(DtsMerchantAccount dtsMerchantAccount) {
        return dtsMerchantAccountMapper.updateByPrimaryKey(dtsMerchantAccount);
    }

    /**
     * 获取商户可提现金额
     * @param brandId
     * @return
     */
    public BigDecimal getWithdrawableAmount(Integer brandId) {
        if (brandId == null) {
            throw new RuntimeException("程序内部错误！未正常传入商户ID！");
        }
        // 当前账号（已统计的数据）
        DtsMerchantAccount dtsMerchantAccount = getAccountByBrandId(brandId);
        BigDecimal remainAmount = dtsMerchantAccount == null ? new BigDecimal(0) : dtsMerchantAccount.getRemainAmount();

        /**
         * 未统计的营业数据
         * 只考虑当天未统计，忽略第二天统计账单job未执行之前的查询，这里存在一段时间上一个交易日尚未统计的空隙时间
         * todo 后续需优化 当日账单job未执行，即上一个交易日尚未统计造成的部分账号总金额修补的问题
         *  可验证最后统计日期之后，该商户是否有未统计的账单数据，存在则补充缺失的统计，job则需要判断交易是否已经统计
         */
        String currday = DateTimeUtils.getDateDisplayString(LocalDateTime.now());
        DtsMerchantDailyStatis dtsMerchantDailyStatis = merchantDailyStatis(brandId, currday);
        // 账号总金额（剩余+当天收入）
        BigDecimal currAmount = dtsMerchantDailyStatis == null ? new BigDecimal(0) : dtsMerchantDailyStatis.getActualAmount();
        BigDecimal totalAmount = remainAmount.add(currAmount);

        BigDecimal applyWithdrawAmount = getApplyWithdrawAmount(brandId);
        // 可提现金额 = 账号总金额（剩余+当天收入）- 今日总收入 - 已申请提现中金额
        return totalAmount.subtract(currAmount).subtract(applyWithdrawAmount);
    }
}
